﻿using System;
using System.Text;

using Evaluant.Uss;
using Models = Evaluant.Uss.Models;
using Evaluant.Uss.SqlMapper;
using Evaluant.Uss.DomainModel.OptionModel;
using Evaluant.Uss.MappingGenerator.TypeConverter;

namespace MappingGenerator
{
    public abstract class IMappingImpl : IMapping
    {
        protected Option _option;
        protected const string IDFIELD = "Id";

        protected Models.Model _model;

        public IMappingImpl(Models.Model model, Option option)
        {
            _model = model;
            _option = option;

            CheckOption(option);
        }

        public abstract Mapping CreateMapping();

        /// <summary>
        /// Check if a type in the domain file correspond to each entity option
        /// </summary>
        /// <param name="option"></param>
        protected void CheckOption(Option option)
        {
            foreach (EntityOption opt in option.EntityOptions)
                if (_model.GetEntity(opt.ClassName) == null)
                    throw new Evaluant.Uss.UniversalStorageException(string.Concat("Cannot find the type ", opt.ClassName, " in your model file"));
        }

        /// <summary>
        /// Gets the name of the db type file (in the same folder as running program)
        /// </summary>
        /// <returns></returns>
        protected string GetDbTypeFileName()
        {
            return String.Concat(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "\\dbtypes.xml");
        }

        /// <summary>
        /// Builds a relationship mapping.
        /// </summary>
        /// <param name="relation">Relation.</param>
        /// <param name="mapping">Mapping.</param>
        protected void BuildRelationshipMapping(Models.Reference relation, Mapping mapping, EntityMapping parentEntity, EntityMapping childEntity)
        {
            ReferenceMapping rm = new ReferenceMapping();
            rm.EntityParent = parentEntity;
            rm.EntityChild = childEntity.Type;
            rm.Name = relation.Name;

            parentEntity.References.Add(rm);

            string parentId = parentEntity.Ids[0].Field;
            string childId = childEntity.Ids[0].Field;

            if (relation.FromMany && relation.ToMany)
            {
                // Many to many relationship
                RuleMapping r1 = new RuleMapping();
                r1.ParentField = parentId;
                r1.ChildField = String.Concat("FK_", "ParentID");
                r1.ParentTable = parentEntity.Table;
                r1.ChildTable = String.Concat(EntityOption.GetTableName(parentEntity.Type), "_", relation.Name);

                if (_option.Constraint)
                    r1.Constraint = String.Concat(r1.ParentTable, "_", r1.ChildField);

                rm.Rules.Add(r1);

                RuleMapping r2 = new RuleMapping();
                r2.ParentField = String.Concat("FK_", "ChildID");
                r2.ChildField = childId;
                r2.ParentTable = r1.ChildTable;
                r2.ChildTable = childEntity.Table;

                if (_option.Constraint)
                    r2.Constraint = String.Concat(r2.ParentTable, "_", r2.ChildField);

                rm.Rules.Add(r2);
            }
            else if (!relation.FromMany && relation.ToMany)
            {
                // One To Many
                RuleMapping r1 = new RuleMapping();
                r1.ParentField = parentId;
                r1.ChildField = String.Concat("FK_", relation.Name);
                r1.ParentTable = parentEntity.Table;
                r1.ChildTable = childEntity.Table;

                if (_option.Constraint)
                    r1.Constraint = String.Concat(r1.ParentTable, "_", r1.ChildField);

                rm.Rules.Add(r1);
            }
            else
            {
                // Many to one / One to one
                RuleMapping r1 = new RuleMapping();
                r1.ParentField = String.Concat("FK_", relation.Name);
                r1.ChildField = childId;
                r1.ParentTable = parentEntity.Table;
                r1.ChildTable = childEntity.Table;

                if (_option.Constraint)
                    r1.Constraint = String.Concat(r1.ParentTable, "_", r1.ChildField);

                rm.Rules.Add(r1);
            }
        }

        /// <summary>
        /// Builds an entity mapping.
        /// </summary>
        /// <param name="c">C.</param>
        /// <param name="entityType">Type of the entity.</param>
        /// <param name="entityOption">Entity option.</param>
        /// <returns></returns>
        protected EntityMapping BuildEntityMapping(Models.Entity entity, EntityOption entityOption)
        {
            EntityMapping em = new EntityMapping();
            em.Type = entity.Type;
            em.Table = entityOption.TableName;

            if (entityOption.DiscriminatorField != String.Empty && entityOption.DiscriminatorField != null)
            {
                em.DiscriminatorField = entityOption.DiscriminatorField;
                em.DiscriminatorValue = entityOption.DiscriminatorValue;
            }
            else
            {
                em.DiscriminatorField = null;
                em.DiscriminatorValue = null;
            }

            AttributeMappingCollection attributes = new AttributeMappingCollection();

            int index = 0;

            /* Inteface management
                        if(c.Implement != string.Empty)
                            foreach(Property prop in ((Interface) _Interfaces[c.Implement]).Properties)
                                if(!props.Contains(prop))
                                    props.Add(prop);
            */

            DBTypeConverter conv = new DBTypeConverter(GetDbTypeFileName());

            foreach (Models.Attribute a in entity.Attributes)
            {
                AttributeMapping am = new AttributeMapping();
                am.Name = a.Name;

                if (a.TypeName == null || a.TypeName == string.Empty)
                    throw new Evaluant.Uss.UniversalStorageException(string.Concat("Undefined property name in ", entity.Type));

                DataType type = conv.Types.GetDataType(a.Type);

                if (type == null)
                    throw new Evaluant.Uss.UniversalStorageException(String.Format("Data type not found: '{0}'", a.TypeName));

                am.DbType = type.DbType;

                if (type.Properties.GetDataProperty("size") != null)
                {
                    am.Size = Convert.ToInt32(type.Properties.GetDataProperty("size").Value);
                }

                if (type.Properties.GetDataProperty("scale") != null)
                {
                    am.Scale = Convert.ToByte(type.Properties.GetDataProperty("scale").Value);
                }

                if (type.Properties.GetDataProperty("precision") != null)
                {
                    am.Precision = Convert.ToByte(type.Properties.GetDataProperty("precision").Value);
                }

                am.ParentEntity = em;
                attributes.Add(am);
                index++;
            }

            em.Attributes = attributes;

            em.CopyNode = null;

            em.Ids.Add(new PrimaryKeyMapping(entityOption.Id.FieldName, em));
            em.Ids[0].Generator = new GeneratorMapping();
            em.Ids[0].Generator.Name = entityOption.Id.Generator;

            if (entityOption.Id.IdProperties.Count > 0)
            {
                em.Ids[0].Generator.Params = new ParamMapping[entityOption.Id.IdProperties.Count];
                index = 0;
                foreach (IdProperty prop in entityOption.Id.IdProperties)
                {
                    ParamMapping param = new ParamMapping();
                    param.Name = prop.Name;
                    param.Value = prop.Value;

                    em.Ids[0].Generator.Params[index] = param;
                    index++;
                }
            }

            return em;
        }

        protected EntityOption GetEntityOption(string className)
        {
            EntityOption entityOption = _option.EntityOptions.GetEntityOptionByName(className);

            if (entityOption == null)
            {
                entityOption = new EntityOption(className);
            }

            if (entityOption.DiscriminatorField != String.Empty && entityOption.DiscriminatorField != null && (entityOption.DiscriminatorValue == String.Empty || entityOption.DiscriminatorValue == null))
                entityOption.DiscriminatorValue = entityOption.ClassName;

            return entityOption;
        }

    }
}